package org.dromara.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysFaultInfoEn;
import org.dromara.system.domain.bo.SysFaultInfoEnBo;
import org.dromara.system.domain.bo.SysFaultInfoEnQueryBo;
import org.dromara.system.domain.bo.SysFaultManageEnBo;
import org.dromara.system.domain.vo.SysFaultInfoEnVo;
import org.dromara.system.domain.vo.SysSolutionDocEnVo;
import org.dromara.system.mapper.SysFaultInfoEnMapper;
import org.dromara.system.service.ISysFaultInfoEnService;
import org.dromara.system.service.ISysSolutionDocEnService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * English Fault Information Service Implementation
 *
 * @author system
 */
@RequiredArgsConstructor
@Service
public class SysFaultInfoEnServiceImpl implements ISysFaultInfoEnService {

    private final SysFaultInfoEnMapper baseMapper;
    private final ISysSolutionDocEnService solutionDocService;

    /**
     * Query fault information list
     *
     * @param queryBo   Query parameters
     * @param pageQuery Pagination parameters
     * @return Fault information paginated list
     */
    @Override
    public TableDataInfo<SysFaultInfoEnVo> queryPageList(SysFaultInfoEnQueryBo queryBo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysFaultInfoEn> lqw = buildQueryWrapper(queryBo);
        Page<SysFaultInfoEnVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    private LambdaQueryWrapper<SysFaultInfoEn> buildQueryWrapper(SysFaultInfoEnQueryBo queryBo) {
        LambdaQueryWrapper<SysFaultInfoEn> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(queryBo.getExcavatorModel()), SysFaultInfoEn::getExcavatorModel, queryBo.getExcavatorModel());
        lqw.eq(StringUtils.isNotBlank(queryBo.getFaultPart()), SysFaultInfoEn::getFaultPart, queryBo.getFaultPart());
        lqw.eq(StringUtils.isNotBlank(queryBo.getFaultSymptom()), SysFaultInfoEn::getFaultSymptom, queryBo.getFaultSymptom());

        // Fault detail fuzzy query (search in fault detail and remark)
        if (StringUtils.isNotBlank(queryBo.getFaultDetail())) {
            lqw.and(wrapper -> wrapper
                .like(SysFaultInfoEn::getFaultDetail, queryBo.getFaultDetail())
                .or()
                .like(SysFaultInfoEn::getRemark, queryBo.getFaultDetail())
            );
        }
        lqw.orderByDesc(SysFaultInfoEn::getCreateTime);
        return lqw;
    }

    /**
     * Query fault information list (non-paginated)
     *
     * @param queryBo Query parameters
     * @return Fault information list
     */
    @Override
    public List<SysFaultInfoEnVo> queryList(SysFaultInfoEnQueryBo queryBo) {
        return baseMapper.selectFaultInfoWithDocList(
            queryBo.getExcavatorModel(),
            queryBo.getFaultPart(),
            queryBo.getFaultSymptom(),
            queryBo.getFaultDetail()
        );
    }

    /**
     * Query fault information detail
     *
     * @param faultId Fault ID
     * @return Fault information detail
     */
    @Override
    public SysFaultInfoEnVo queryById(Long faultId) {
        // Optimization: Query directly by ID, avoid full table scan
        return baseMapper.selectFaultInfoWithDocById(faultId);
    }

    @Override
    public List<String> getExcavatorModelList() {
        return baseMapper.selectDistinctExcavatorModels();
    }

    @Override
    public List<String> getFaultPartList(String excavatorModel) {
        return baseMapper.selectDistinctFaultParts(excavatorModel);
    }

    @Override
    public List<String> getFaultSymptomList(String excavatorModel, String faultPart) {
        return baseMapper.selectDistinctFaultSymptoms(excavatorModel, faultPart);
    }

    @Override
    public List<Map<String, Object>> getFaultDetailList(String excavatorModel, String faultPart, String faultSymptom) {
        LambdaQueryWrapper<SysFaultInfoEn> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(excavatorModel), SysFaultInfoEn::getExcavatorModel, excavatorModel);
        lqw.eq(StringUtils.isNotBlank(faultPart), SysFaultInfoEn::getFaultPart, faultPart);
        lqw.eq(StringUtils.isNotBlank(faultSymptom), SysFaultInfoEn::getFaultSymptom, faultSymptom);
        lqw.orderByDesc(SysFaultInfoEn::getCreateTime);

        List<SysFaultInfoEn> faultInfos = baseMapper.selectList(lqw);

        return faultInfos.stream().map(fault -> {
            Map<String, Object> map = new HashMap<>();
            map.put("faultId", fault.getFaultId());
            map.put("faultDetail", fault.getFaultDetail());
            map.put("solutionId", fault.getSolutionId());
            map.put("createTime", fault.getCreateTime());
            return map;
        }).collect(Collectors.toList());
    }

    /**
     * Add fault information
     *
     * @param bo Fault information
     * @return Result
     */
    @Override
    public Boolean insertByBo(SysFaultInfoEnBo bo) {
        SysFaultInfoEn add = MapstructUtils.convert(bo, SysFaultInfoEn.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setFaultId(add.getFaultId());
        }
        return flag;
    }

    /**
     * Update fault information
     *
     * @param bo Fault information
     * @return Result
     */
    @Override
    public Boolean updateByBo(SysFaultInfoEnBo bo) {
        SysFaultInfoEn update = MapstructUtils.convert(bo, SysFaultInfoEn.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * Data validation before save
     */
    private void validEntityBeforeSave(SysFaultInfoEn entity) {
        // TODO Data validation, such as unique constraints
    }

    /**
     * Batch delete fault information
     *
     * @param ids Fault information IDs to delete
     * @return Result
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // TODO Business validation, determine if validation is needed
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysFaultInfoEnVo createWithSolution(SysFaultInfoEnBo faultBo,
                                             MultipartFile[] files,
                                             String folderName,
                                             String remark,
                                             String action,
                                             Long linkDocId) {
        // 1) Create fault first
        this.insertByBo(faultBo);

        // 2) Handle solution
        Long solutionId = null;
        if ("upload".equalsIgnoreCase(action)) {
            var docVo = solutionDocService.uploadPdfFolderSolution(files, folderName, remark);
            solutionId = docVo.getDocId();
        } else if ("link".equalsIgnoreCase(action)) {
            solutionId = linkDocId;
        }

        if (solutionId != null) {
            SysFaultInfoEnBo updateBo = new SysFaultInfoEnBo();
            updateBo.setFaultId(faultBo.getFaultId());
            updateBo.setSolutionId(solutionId);
            this.updateByBo(updateBo);
        }

        return this.queryById(faultBo.getFaultId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysFaultInfoEnVo updateWithSolution(SysFaultInfoEnBo faultBo,
                                             MultipartFile[] files,
                                             String folderName,
                                             String remark,
                                             String action,
                                             Long linkDocId,
                                             Boolean deleteOld) {
        // 1) Update fault basic information
        this.updateByBo(faultBo);

        // 2) Query current fault, get old solutionId
        SysFaultInfoEnVo current = this.queryById(faultBo.getFaultId());
        Long oldSolutionId = current != null ? current.getSolutionId() : null;

        // 3) Handle according to action
        Long newSolutionId = oldSolutionId;
        if ("replace_upload".equalsIgnoreCase(action)) {
            var docVo = solutionDocService.uploadPdfFolderSolution(files, folderName, remark);
            newSolutionId = docVo.getDocId();
            // Delete old solution (only when not referenced by other faults, or explicitly required to delete)
            if (oldSolutionId != null && (Boolean.TRUE.equals(deleteOld))) {
                int ref = baseMapper.countBySolutionId(oldSolutionId);
                if (ref <= 1) {
                    // Only this fault is using, can delete
                    solutionDocService.deleteWithValidByIds(java.util.List.of(oldSolutionId), false);
                }
            }
        } else if ("link".equalsIgnoreCase(action)) {
            newSolutionId = linkDocId;
        } else if ("remove".equalsIgnoreCase(action)) {
            newSolutionId = null;
            if (oldSolutionId != null && Boolean.TRUE.equals(deleteOld)) {
                int ref = baseMapper.countBySolutionId(oldSolutionId);
                if (ref <= 1) {
                    solutionDocService.deleteWithValidByIds(java.util.List.of(oldSolutionId), false);
                }
            }
        } // keep: No change

        // 4) Write back new solutionId
        SysFaultInfoEnBo updateBo = new SysFaultInfoEnBo();
        updateBo.setFaultId(faultBo.getFaultId());
        updateBo.setSolutionId(newSolutionId);
        this.updateByBo(updateBo);

        return this.queryById(faultBo.getFaultId());
    }

    // ==================== Integrated Management Interface Implementation ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysFaultInfoEnVo createFaultWithSolution(SysFaultManageEnBo manageBo, MultipartFile[] files) {
        // 0. Compatibility inference: When solutionAction not explicitly provided, auto-infer based on request content
        String inferredAction = manageBo.getSolutionAction();
        boolean hasFiles = files != null && files.length > 0;
        if (StringUtils.isBlank(inferredAction) || "none".equals(inferredAction)) {
            if (hasFiles) {
                inferredAction = "upload";
            } else if (manageBo.getLinkDocId() != null) {
                inferredAction = "link";
            } else {
                inferredAction = "none";
            }
            // Write back, reuse existing needUploadSolution/needLinkSolution judgment
            manageBo.setSolutionAction(inferredAction);
        }

        // 1. Validate parameters
        if (manageBo.needUploadSolution() && (files == null || files.length == 0)) {
            throw new RuntimeException("Files must be provided when uploading solution");
        }
        if (manageBo.needLinkSolution() && manageBo.getLinkDocId() == null) {
            throw new RuntimeException("Solution ID must be provided when linking solution");
        }

        // 2. Create fault information
        SysFaultInfoEnBo faultBo = manageBo.toFaultInfoBo();
        this.insertByBo(faultBo);

        // 3. Handle solution
        Long solutionId = null;
        if (manageBo.needUploadSolution()) {
            // Upload new solution
            SysSolutionDocEnVo docVo = solutionDocService.uploadPdfFolderSolution(
                files, manageBo.getSolutionFolderName(), manageBo.getSolutionRemark());
            solutionId = docVo.getDocId();
        } else if (manageBo.needLinkSolution()) {
            // Link existing solution
            solutionId = manageBo.getLinkDocId();
        }

        // 4. Update fault information solution link
        if (solutionId != null) {
            SysFaultInfoEnBo updateBo = new SysFaultInfoEnBo();
            updateBo.setFaultId(faultBo.getFaultId());
            updateBo.setSolutionId(solutionId);
            this.updateByBo(updateBo);
        }

        // 5. Return complete fault information
        return this.queryById(faultBo.getFaultId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysFaultInfoEnVo updateFaultWithSolution(SysFaultManageEnBo manageBo, MultipartFile[] files) {
        // 1. Validate parameters
        if (manageBo.getFaultId() == null) {
            throw new RuntimeException("Fault ID cannot be empty when updating");
        }
        if (manageBo.needUploadSolution() && (files == null || files.length == 0)) {
            throw new RuntimeException("Files must be provided when uploading solution");
        }
        if (manageBo.needLinkSolution() && manageBo.getLinkDocId() == null) {
            throw new RuntimeException("Solution ID must be provided when linking solution");
        }

        // 2. Query current fault information
        SysFaultInfoEnVo currentFault = this.queryById(manageBo.getFaultId());
        if (currentFault == null) {
            throw new RuntimeException("Fault information does not exist");
        }
        Long oldSolutionId = currentFault.getSolutionId();

        // 3. Update fault basic information
        SysFaultInfoEnBo faultBo = manageBo.toFaultInfoBo();
        this.updateByBo(faultBo);

        // 4. Handle solution
        Long newSolutionId = oldSolutionId;

        if ("replace_upload".equals(manageBo.getSolutionAction())) {
            // Replace and upload new solution
            SysSolutionDocEnVo docVo = solutionDocService.uploadPdfFolderSolution(
                files, manageBo.getSolutionFolderName(), manageBo.getSolutionRemark());
            newSolutionId = docVo.getDocId();

            // Delete old solution (if needed and no other references)
            if (oldSolutionId != null && Boolean.TRUE.equals(manageBo.getDeleteOldSolution())) {
                deleteSolutionIfNotReferenced(oldSolutionId, manageBo.getFaultId());
            }
        } else if (manageBo.needLinkSolution()) {
            // Link existing solution
            newSolutionId = manageBo.getLinkDocId();
            
            // Delete old solution (if needed and no other references)
            if (oldSolutionId != null && Boolean.TRUE.equals(manageBo.getDeleteOldSolution())) {
                deleteSolutionIfNotReferenced(oldSolutionId, manageBo.getFaultId());
            }
        } else if (manageBo.needRemoveSolution()) {
            // Remove solution link
            newSolutionId = null;
            
            // Delete old solution (if needed and no other references)
            if (oldSolutionId != null && Boolean.TRUE.equals(manageBo.getDeleteOldSolution())) {
                deleteSolutionIfNotReferenced(oldSolutionId, manageBo.getFaultId());
            }
        }
        // keepSolution() or "none": Keep unchanged

        // 5. Update solution link
        SysFaultInfoEnBo updateBo = new SysFaultInfoEnBo();
        updateBo.setFaultId(manageBo.getFaultId());
        updateBo.setSolutionId(newSolutionId);
        this.updateByBo(updateBo);

        // 6. Return complete fault information
        return this.queryById(manageBo.getFaultId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteFaultWithCascade(Collection<Long> ids, String cascadeStrategy) {
        // Handle solution for each fault
        for (Long id : ids) {
            SysFaultInfoEnVo vo = this.queryById(id);
            Long solutionId = vo != null ? vo.getSolutionId() : null;
            
            if (solutionId != null) {
                boolean shouldDelete = "yes".equalsIgnoreCase(cascadeStrategy)
                    || ("auto".equalsIgnoreCase(cascadeStrategy) && 
                        baseMapper.countBySolutionId(solutionId) <= 1);
                
                if (shouldDelete) {
                    solutionDocService.deleteWithValidByIds(java.util.List.of(solutionId), false);
                }
            }
        }
        
        // Delete fault information
        return this.deleteWithValidByIds(ids, false);
    }

    /**
     * Delete solution (if not referenced by other faults)
     */
    private void deleteSolutionIfNotReferenced(Long solutionId, Long excludeFaultId) {
        if (solutionId == null) return;
        
        int refCount = baseMapper.countBySolutionId(solutionId);
        // If only referenced by current fault, can delete
        if (refCount <= 1) {
            solutionDocService.deleteWithValidByIds(java.util.List.of(solutionId), false);
        }
    }

    // ==================== Compatibility Method Implementation ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithCascade(Collection<Long> ids, String cascadeStrategy) {
        return deleteFaultWithCascade(ids, cascadeStrategy);
    }
}

